﻿using System;
using Reflector;
using Reflector.CodeModel;
using System.Collections.Generic;
using MethodSeqGenerator;
using Reflector.Graph.Graphs;
using System.Windows.Forms;
using System.Collections;
using System.IO;
using Microsoft.Msagl.GraphViewerGdi;
using Microsoft.Msagl.Drawing;

internal class SeqGenerator : IPackage
{
    //Reflector related members
    private IWindowManager windowManager;
    private ICommandBarManager commandbarManager = null;
    private ICommandBarButton menuBarButton = null;
    private ICommandBarButton menuBarPreprocessButton = null;
    private ICommandBarSeparator menuBarSeparator = null;
    private EventHandler evtSearch = null;
    private EventHandler evtSearchPreprocess = null;
    private IServiceProvider serviceProvider;
    private ITranslatorManager translatorManager;
    private IAssemblyManager assemblyManager = null;

    //Static members 
    public static Hashtable methodNodes = new Hashtable();
    public static StreamWriter swSequenceOutput = null;
    public static StreamWriter swLogging = null;
    public static HashSet<String> primitiveDataTypes = null;
    public static HashSet<String> newSearchStrings = null;      //A Hashtable that stores the set of
                                                            //unknown types at the end of each iteration
    public static int NUM_ITERATIONS = 5;   //As our new approach gathers complete sequences instead
                                            //of partial sequences, we need to repeat again to gather the
                                            //additional sequences

    public static String DLL_LOCATION_DIR = "C:\\Suresh\\MicrosoftPex\\NETReflectorAddins\\Source\\MethodSeqGenerator\\DLLInputs";
    public static String FACTORY_OUTPUT_DIR = "C:\\Suresh\\MicrosoftPex\\NETReflectorAddins\\Source\\MethodSeqGenerator\\Factories";
    public static String INP_CLASS_FILENAME = "InpClasses.txt";
    public static String PRE_PROCESSOR_FILENAME = "PreprocessorOut.txt";
    public static String IGNORE_DLLS = "IgnoreDLLs.txt";
    public static HashSet<String> ignoreDLLSet = null;

    public static List<String> searchStringList = null;
    public static List<String> alreadySearchedStringList = null;
    public static String currentDLL = "";    
    public static String currentMethodName = "";
    public static String currentTypeDeclarationName = "";

    public static Hashtable centralSequenceStore = null;   //A global store including all possible sequences
    public static Hashtable searchStringToNodes = null; //A store locally used for each method declaration
    public GViewer graphViewer;
    public Microsoft.Msagl.Drawing.Graph graphDrawing = null;

    //Target namespace set : A set that stores what are the target namespace classes
    //to avoid generating unwanted classes.
    public static HashSet<String> targetNamespaces = null;
    
    public void Load(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
        this.windowManager = (IWindowManager)serviceProvider.GetService(typeof(IWindowManager));
        this.commandbarManager = (ICommandBarManager)serviceProvider.GetService(typeof(ICommandBarManager));        
        this.translatorManager = (ITranslatorManager)serviceProvider.GetService(typeof(ITranslatorManager));
        this.assemblyManager = (IAssemblyManager)serviceProvider.GetService(typeof(IAssemblyManager));
         
        //this.windowManager.ShowMessage("Loading HelloWorld!");

        evtSearch = new EventHandler(this.SequenceGenButton_Click);
        evtSearchPreprocess = new EventHandler(this.ProprocessDLLButton_Click);
        this.menuBarSeparator = commandbarManager.CommandBars["Tools"].Items.AddSeparator();
        this.menuBarButton = commandbarManager.CommandBars["Tools"].Items.AddButton("Generate Sequence", null, evtSearch);
        this.menuBarPreprocessButton = commandbarManager.CommandBars["Tools"].Items.AddButton("Preprocess DLLs", null, evtSearchPreprocess);
        
        swSequenceOutput = new StreamWriter(new FileStream("Sequences.txt", FileMode.Create, FileAccess.Write));
        swLogging = new StreamWriter(new FileStream("Logging.txt", FileMode.Create, FileAccess.Write));
        graphViewer = new Microsoft.Msagl.GraphViewerGdi.GViewer();
        graphDrawing = new Microsoft.Msagl.Drawing.Graph("graph for visualization");

        //Load the primitive data types
        LoadPrimitiveTypes();

        //loading the dll list that needs to be ignored
        loadIgnoreDLLs();
        targetNamespaces = new HashSet<string>();
        targetNamespaces.Add("System");
    }

    public void Unload()
    {
        this.commandbarManager.CommandBars["Tools"].Items.Remove(this.menuBarButton);
        this.commandbarManager.CommandBars["Tools"].Items.Remove(this.menuBarPreprocessButton);
        this.commandbarManager.CommandBars["Tools"].Items.Remove(this.menuBarSeparator);
        swSequenceOutput.Close();
        swLogging.Close();
    }

    private void ProprocessDLLButton_Click(object sender, EventArgs e)
    {
        DLLPreprocessor dpObj = new DLLPreprocessor(serviceProvider, translatorManager);
        dpObj.AnalyzeDll();

        //Output the external classes entries
        StreamWriter swPreProcOutput = new StreamWriter(new FileStream(PRE_PROCESSOR_FILENAME, FileMode.Create, FileAccess.Write));
        swPreProcOutput.WriteLine("################External Classes################");
        foreach (String externalCls in DLLPreprocessor.externalClassList)
        {
            swPreProcOutput.WriteLine(externalCls);
        }

        swPreProcOutput.WriteLine("################Internal Classes################");
        foreach (String internalCls in DLLPreprocessor.declaredClassList)
        {
            swPreProcOutput.WriteLine(internalCls);
        }

        swPreProcOutput.WriteLine("################Internal Interfaces################");
        foreach (String internalFace in DLLPreprocessor.declaredInterfaceList)
        {
            swPreProcOutput.WriteLine(internalFace);
        }
        swPreProcOutput.Close();
    }
    
    /**
     * Event handler invoked when generation of method sequences is started from Reflector
     **/
    private void SequenceGenButton_Click(object sender, EventArgs e)
    {
        DateTime begindt = DateTime.Now;
        swLogging.WriteLine("Starting time: (" + begindt.Date + ") " + begindt.Hour + " : " + begindt.Minute + " : " + begindt.Second + " : " + begindt.Millisecond);
                
        newSearchStrings = new HashSet<String>();
        centralSequenceStore = new Hashtable();
        searchStringToNodes = new Hashtable();
        alreadySearchedStringList = new List<string>();

        //Call to load all input search strings
        LoadSearchStrings();

        //Initiating the main process
        StartProcess(); 
        
        DateTime enddt = DateTime.Now;
        TimeSpan tsObj = enddt - begindt;
        swLogging.WriteLine("Ending time: (" + enddt.Date + ") " + enddt.Hour + " : " + enddt.Minute + " : " + enddt.Second + " : " + enddt.Millisecond);
        swLogging.WriteLine("Total time taken: " + tsObj.Hours + " : " + tsObj.Minutes + " : " + tsObj.Seconds + " : " + tsObj.Milliseconds);
        swLogging.Flush();
    }

    /**
     * Main process that starts the entire search. 
     **/
    public void StartProcess()
    {
        //While searching, don't search for the classes including namespaces
        MCodeSearch myCs = new MCodeSearch();
        int iter_count = 0;

        //Populate all search strings into already searched strings
        //to avoid them adding to unknown list
        foreach (String typeName in searchStringList)
            alreadySearchedStringList.Add(typeName);        

        CodeGenerator cgObj = new CodeGenerator("facebook.Factories");

        while (searchStringList.Count != 0 && iter_count < NUM_ITERATIONS)
        {
            //Search for relevant method declarations for each class and analyze
            //in parallel to gather sequences.
            myCs.SearchInTheCode(searchStringList, serviceProvider, this);
            
            //Move all unknown types belonging to the target namespace into search strings and recompute sequences
            //for those unknown types
            searchStringList.Clear();
            foreach (String typeName in newSearchStrings)
            {
                int indexOfDot = typeName.IndexOf(".");
                if(indexOfDot == -1 || indexOfDot == 0)
                    continue;

                String firstPart = typeName.Substring(0, indexOfDot);
                if (targetNamespaces.Contains(firstPart))
                {
                    searchStringList.Add(typeName);
                    alreadySearchedStringList.Add(typeName);
                }                
            }
            
            newSearchStrings.Clear();
            iter_count++;

            //Debugging : Classes used in each iteration
            swLogging.WriteLine("Iteration Number: " + iter_count);
            foreach (String ssStr in searchStringList)
                swLogging.WriteLine(ssStr);

            cgObj.GenerateCode(SeqGenerator.centralSequenceStore);
            SeqGenerator.centralSequenceStore.Clear();
        }

        //Printing the remaining unknown types
        if (searchStringList.Count > 0)
        {
            SeqGenerator.swSequenceOutput.WriteLine("Unknown Types::::");
            foreach (String unknownSearchString in searchStringList)
                SeqGenerator.swSequenceOutput.WriteLine(unknownSearchString);
        }    
        SeqGenerator.swSequenceOutput.Flush();
        swLogging.Flush();
    }

    //Function that analyzes each method declaration by constructing the statement graph
    public void AnalyzeMethodDeclaration(IMethodDeclaration imdObj, List<String> searchStringList)
    {
        //graphDrawing = new Microsoft.Msagl.Drawing.Graph("graph for visualization");    //Initializing a new graph drawing
                
        // resolve body
        methodNodes.Clear();
        searchStringToNodes.Clear();

        try
        {
            IMethodDeclaration visitedMethod = this.translatorManager.CreateDisassembler(null, null).TranslateMethodDeclaration(imdObj);
            StatementGraph graph = new StatementGraph(visitedMethod);

            //explore vertex
            SeqGenStatementGraphVertexPopulatorVisitor vertexVisitor = new SeqGenStatementGraphVertexPopulatorVisitor(graph, searchStringList);
            //vertexVisitor.setGraphDrawing(graphDrawing);
            vertexVisitor.VisitMethodDeclaration(visitedMethod);

            //explore edges
            StatementGraphEdgePopulatorVisitor edgeVisitor = new StatementGraphEdgePopulatorVisitor(graph);
            //edgeVisitor.setGraphDrawing(graphDrawing);
            edgeVisitor.VisitMethodDeclaration(visitedMethod);       

            GraphAnalyzer gaObj = new GraphAnalyzer(graph);
            //gaObj.DepthFirstTraversal(graph.Root, visitedMethod);

            //Get the nodes of interest            
            foreach (String searchString in searchStringToNodes.Keys)
            {
                HashSet<IStatement> interestingNodeList = (HashSet<IStatement>)searchStringToNodes[searchString];
                foreach (IStatement interestingNode in interestingNodeList)
                {
                    try
                    {
                        gaObj.ExtractSequence(interestingNode, searchString);
                    }
                    catch (Exception ex) 
                    {
                        //swLogging.WriteLine(ex.Message + "DLLName: " + currentDLL + ", Class: " +
                        //    currentTypeDeclarationName  + ", Method: " + imdObj.Name);
                    }
                }
            }
        }
        catch (System.Exception ex)
        {
            //Log the exception and proceed further for other method declarations
            //SeqGenerator.swLogging.WriteLine("DLLName:" + SeqGenerator.currentDLL + ", Class:"
            //                + SeqGenerator.currentTypeDeclarationName + ", Methodname:"
            //                + SeqGenerator.currentMethodName + "Message:" + ex.Message);
            return;
        }

        /*if (!bRetVal && vertexVisitor.getRelatedMIHSet().Count > 0)
        {
            //If no sequence is found, then print all related method invocations
            SeqGenerator.swSequenceOutput.WriteLine("==========================");
            SeqGenerator.swSequenceOutput.WriteLine("Related method calls for " + searchString + ":");
            SeqGenerator.swSequenceOutput.WriteLine("DLLName: " + SeqGenerator.currentDLL + ", MethodName: " + SeqGenerator.currentMethodName);

            foreach (MethodInvocationHolder mchObj in vertexVisitor.getRelatedMIHSet())
            {
                SeqGenerator.swSequenceOutput.WriteLine(mchObj);
            }

            SeqGenerator.swSequenceOutput.WriteLine("");
            SeqGenerator.swSequenceOutput.WriteLine("==========================");
        }*/

        //Viewing the graph that was drawn
        /*graphViewer.Graph = this.graphDrawing;
        System.Windows.Forms.Form form = new System.Windows.Forms.Form();
        form.SuspendLayout();
        graphViewer.Dock = System.Windows.Forms.DockStyle.Fill;
        form.Controls.Add(graphViewer);
        form.ResumeLayout();
        form.ShowDialog();*/
    }

    /**
     * MSeqGen provides option to ignore dependent dlls from processing. This list has to be
     * provided in an input file
     **/ 
    public void loadIgnoreDLLs()
    {
        ignoreDLLSet = new HashSet<string>();
        StreamReader tr = new StreamReader(IGNORE_DLLS);        
        String inpStr;
        while ((inpStr = tr.ReadLine()) != null)
        {
            if (inpStr.StartsWith("#"))
            {
                continue;
            }
            ignoreDLLSet.Add(inpStr);
        }
        tr.Close();
    }

    public void LoadPrimitiveTypes()
    {
        primitiveDataTypes = new HashSet<string>();
        primitiveDataTypes.Add("byte");
        primitiveDataTypes.Add("System.Byte");
        primitiveDataTypes.Add("sbyte");
        primitiveDataTypes.Add("System.SByte");
        primitiveDataTypes.Add("int");
        primitiveDataTypes.Add("System.Int32");
        primitiveDataTypes.Add("uint");
        primitiveDataTypes.Add("System.UInt32");
        primitiveDataTypes.Add("short");
        primitiveDataTypes.Add("System.Int16");
        primitiveDataTypes.Add("ushort");
        primitiveDataTypes.Add("System.UInt16");
        primitiveDataTypes.Add("long");
        primitiveDataTypes.Add("System.Int64");
        primitiveDataTypes.Add("ulong");
        primitiveDataTypes.Add("System.UInt64");
        primitiveDataTypes.Add("float");
        primitiveDataTypes.Add("System.Single");
        primitiveDataTypes.Add("double");
        primitiveDataTypes.Add("System.Double");
        primitiveDataTypes.Add("char");
        primitiveDataTypes.Add("System.Char");
        primitiveDataTypes.Add("bool");
        primitiveDataTypes.Add("System.Boolean");
        primitiveDataTypes.Add("object");
        primitiveDataTypes.Add("System.Object");
        primitiveDataTypes.Add("string");
        primitiveDataTypes.Add("System.String");
        primitiveDataTypes.Add("decimal");
        primitiveDataTypes.Add("System.Decimal");
        primitiveDataTypes.Add("nil");
    }

    public void LoadSearchStrings()
    {
        searchStringList = new List<String>();
        
        StreamReader tr = new StreamReader(INP_CLASS_FILENAME);
        String inpStr;
        while ((inpStr = tr.ReadLine()) != null)
        {
            if (inpStr.StartsWith("#") || inpStr.Equals(""))
            {
                continue;
            }
            searchStringList.Add(inpStr);

            //Get the first part of the class
            int firstIndex = inpStr.IndexOf(".");
            if (firstIndex != -1)
                targetNamespaces.Add(inpStr.Substring(0, firstIndex));            
        }
        tr.Close();
    }
}

